# -*- coding: utf-8 -*-
# @DATE :  2025/8/7 
# @Author: HQ


import pandas as pd
import numpy as np
import json
import ast
import random
import logging
from typing import Optional, Dict, Any
from datetime import datetime, timedelta
from woman_config import *
from task_classification import *
from tag_tools_xyz import *
import matplotlib.pyplot as plt

# 配置日志
logging.basicConfig(
    level=logging.WARNING,  # 只记录警告和错误
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

"""
test_key # 测试时为True， 正式版为False
group_to_output_key 为输出每个板块的分析字段名
"""
test_key = True
app_version = 'v3.12d'
group_to_output_key = {
    '周期长度': 'cycle_anlzr',
    '周期长度差异': 'cycle_diff_anlzr',
    '经期天数': 'period_anlzr',
    '月经量': 'blood_anlzr',
    '皮肤温度': 'temp_anlzr',
    '痛经': 'pain_anlzr',
    '其他症状': 'symptoms_anlzr',
    'hrv与心情': 'hrv_mood_anlzr'

}
period_keys = ['mens', 'folli', 'ovula', 'lm']
today_strf = datetime.today().strftime('%Y-%m-%d')
def change_time(mens_cycle, key):
    date_stage = mens_cycle[key]
    date_stage_strp = datetime.strptime(date_stage, '%Y-%m-%d')

    return date_stage_strp

def check_period_and_date(errors, period, date, name):
    # 检查各阶段
    if period is None:
        if date is not None:
            errors.append(f"{name}：cycle is None but date is not None, data inconsistency")
            return False, errors
        else:
            return True, errors
    elif period <= 0:
        errors.append(f"{name}：cycle is {period},data abnormal")
        return False, errors  # 错误数据先保留，用于结果展示
    else:
        return True, errors


def check_cycles(cycles) :
    """
    检查周期信息是否连续且合理。
    参数：
        cycles: list[dict]，每个字典包含
            - "date": str, 月经第一天 (格式: YYYY-MM-DD)
            - "period_len": int, 月经持续天数

    返回：
        bool: True 表示周期信息合理，False 表示存在异常
    """
    if not cycles:
        return False

    # 按日期排序，避免输入无序
    cycles_sorted = sorted(cycles, key=lambda x: x['mens_date'])

    for i in range(len(cycles_sorted) - 1):
        curr = cycles_sorted[i]
        nxt = cycles_sorted[i + 1]

        curr_start = datetime.strptime(curr['mens_date'], "%Y-%m-%d")
        curr_end = curr_start + timedelta(days=curr["period_len"] - 1)
        nxt_start = datetime.strptime(nxt['mens_date'], "%Y-%m-%d")

        if nxt_start <= curr_end:
            # 出现重叠或异常
            return False

    return True

def create_every_cycles(menstrual_main_page_history):
    """
    每个周期分为四个时期
    """
    error = {'error_type': 1, 'error_content': 'create_every_cycles error',
                 'error_date': datetime.strftime(datetime.today().date(), date_format)}
    # print('输入的数据周期',menstrual_main_page_history)
    menstrual_main_click_dates = [datetime.strptime(date['mens_date'], date_format) for date in
                                  menstrual_main_page_history]
    menstrual_main_click_dates = sorted(menstrual_main_click_dates, reverse=False)

    menstrual_cycles = []
    # try:
    for i in range(len(menstrual_main_click_dates) - 1):
        mens_date_last = menstrual_main_click_dates[i]
        mens_date_next = menstrual_main_click_dates[i+1]
        period_len_cur = next((item['period_len'] for item in menstrual_main_page_history if
                               datetime.strptime(item['mens_date'], date_format) == mens_date_last), None)
        diff_day = (mens_date_next - mens_date_last).days

        if diff_day > 20:
            ovul_pred_date = mens_date_last + timedelta(np.round(-8.2835 + 0.7344 * diff_day))
        else:
            ovul_pred_date = mens_date_last + timedelta(np.round(-5.2835 + 0.7344 * diff_day))
        mens_date_next_str = datetime.strftime(mens_date_next, date_format)
        mens_date_last_str = datetime.strftime(mens_date_last, date_format)
        ovul_pred_date_str = datetime.strftime(ovul_pred_date, date_format)

        fle_pred_date = mens_date_last + timedelta(days=period_len_cur)
        fle_pred_date_str = datetime.strftime(fle_pred_date, date_format)
        fle_period = (ovul_pred_date - fle_pred_date).days

        lm_pred_date = ovul_pred_date + timedelta(days=7)
        lm_pred_date_str = datetime.strftime(lm_pred_date, date_format)
        lm_period = (mens_date_next - lm_pred_date).days

        mens_cycle = {'mens_next_date': mens_date_next_str,
                      'mens_pred_date': mens_date_last_str,
                      'mens_cycle': diff_day,
                      'mens_period': period_len_cur,
                      'follicle_pred_date': fle_pred_date_str,
                      'follicle_pred_date_period': fle_period,
                      'ovul_pred_date': ovul_pred_date_str,
                      'ovul_period': 7,
                      'luteum_pred_date': lm_pred_date_str,
                      'luteum_period': lm_period

                      }

        pred_tag, err, mens_cycle = validate_cycle(mens_cycle, mens_date_next)
        # print('每个周期的具体数据', pred_tag, err, mens_cycle)
        if pred_tag :
            menstrual_cycles.append(mens_cycle)

            error = {'error_type': 0, 'error_content': None, 'error_date': None}
        else:
            error = {'error_type': 1, 'error_content': f'create_every_cycles error: {err}',
             'error_date': datetime.strftime(datetime.today().date(), date_format)}
            return menstrual_cycles, error
    # except :
    #
    #     error = {'error_type': 1, 'error_content': 'create_every_cycles error',
    #              'error_date': datetime.strftime(datetime.today().date(), date_format)}
    # print('输入输出的内容',menstrual_cycles, error)
    return menstrual_cycles, error


def validate_cycle(mens_pred_cycle, next_mens_date):
    """
    验证计算的月经周期是否符合预期的顺序和连续性
    :param mens_pred_cycle: 计算出的周期数据 (字典)
    :param next_mens_date: 下次周期的第一天
    :return: 是否正确 (bool) 和 错误信息 (str)
    """

    mens_start, mens_end = tf_period_and_date(mens_pred_cycle['mens_pred_date'], mens_pred_cycle['mens_period'])
    fle_start, fle_end = tf_period_and_date(mens_pred_cycle['follicle_pred_date'],
                                            mens_pred_cycle['follicle_pred_date_period'])
    ovul_start, ovul_end = tf_period_and_date(mens_pred_cycle['ovul_pred_date'], mens_pred_cycle['ovul_period'])
    luteum_start, luteum_end = tf_period_and_date(mens_pred_cycle['luteum_pred_date'], mens_pred_cycle['luteum_period'])

    # 规则1：排卵期不得早于月经结束
    if fle_start is not None and mens_end is not None and fle_start < mens_end:
        fle_start = mens_end

    if ovul_start is not None and mens_end is not None and ovul_start <= mens_end:
        ovul_start = mens_end
        fle_start = None

    if luteum_start is not None and mens_end is not None and luteum_start <= mens_end:
        fle_start = None
        ovul_start = None
        luteum_start = mens_end

    # 阶段连续性检查
    # ----------------------------
    errors = []
    # 存在有效卵泡期的情况
    if fle_start is not None:
        # 顺序应为：月经 → 卵泡 → 排卵 → 黄体
        expected_order = [
            [mens_start, mens_end],
            [fle_start, fle_end],
            [ovul_start, ovul_end],
            [luteum_start, luteum_end],
            [next_mens_date, None]
        ]

        for i in range(len(expected_order) - 1):

            prev_end = expected_order[i][1]
            next_start = expected_order[i + 1][0]

            if prev_end > next_start:
                errors.append(f"1phase {i} and {i + 1} overlap")
            elif prev_end != next_start:
                errors.append(f"1phase {i} and {i + 1} have gaps")

            if prev_end != next_start:
                expected_order[i][1] = next_start - pd.Timedelta(days=1)

        follicle_pred_date = datetime.strftime(fle_start, '%Y-%m-%d')
        fle_period = (expected_order[2][0] - expected_order[1][0]).days
        ovulation_predict_date_str = datetime.strftime(ovul_start, '%Y-%m-%d')
        ovul_period = (luteum_start - ovul_start).days

    # 无有效卵泡期的情况
    elif ovul_start is not None and fle_start is None:
        # 顺序应为：月经 → 排卵 → 黄体
        expected_order = [
            [mens_start, mens_end],
            [ovul_start, ovul_end],
            [luteum_start, luteum_end],
            [next_mens_date, None]
        ]
        # 检查时间顺序是否严格递增
        for i in range(len(expected_order) - 1):
            prev_end = expected_order[i][1]
            next_start = expected_order[i + 1][0]
            # print('错误问题',prev_end , next_start,expected_order,luteum_start, luteum_end)
            if prev_end > next_start:
                errors.append(f"2phase {i} and {i + 1} overlap")
            elif prev_end != next_start:
                errors.append(f"2phase {i} and {i + 1} have gap")
        follicle_pred_date = fle_period = None
        ovulation_predict_date_str = ovul_start.strftime('%Y-%m-%d')
        ovul_period = (luteum_start - ovul_start).days
    else:
        # 仅有月经和黄体
        expected_order = [
            [mens_start, mens_end],
            [luteum_start, luteum_end],
            [next_mens_date, None]
        ]
        # 检查时间顺序是否严格递增
        for i in range(len(expected_order) - 1):
            prev_end = expected_order[i][1]
            next_start = expected_order[i + 1][0]

            if prev_end > next_start:
                errors.append(f"3phase {i} and {i + 1} overlap")
            elif prev_end != next_start:
                errors.append(f"3phase {i} and {i + 1} have gap")
        follicle_pred_date = fle_period = None
        ovulation_predict_date_str = ovul_period = None

    lm_pred_date_str = luteum_start.strftime('%Y-%m-%d')
    lm_period = (next_mens_date - luteum_start).days

    tag_mens, errors = check_period_and_date(errors, mens_pred_cycle['mens_period'], mens_pred_cycle['mens_pred_date'],
                                             "mens")
    tag_fle, errors = check_period_and_date(errors, fle_period, follicle_pred_date, "folli")
    tag_ovlu, errors = check_period_and_date(errors, ovul_period, ovulation_predict_date_str, "ovula")
    tag_lm, errors = check_period_and_date(errors, lm_period, lm_pred_date_str, "lm")

    if all([tag_mens, tag_fle, tag_ovlu, tag_lm]):
        # 最终结果输出
        result = {
            'mens_next_date': datetime.strftime(next_mens_date, '%Y-%m-%d'),
            'mens_pred_date': mens_pred_cycle['mens_pred_date'],
            'mens_cycle': mens_pred_cycle['mens_cycle'],
            'mens_period': mens_pred_cycle['mens_period'],
            'follicle_pred_date': follicle_pred_date,
            'follicle_period': fle_period,
            'ovul_pred_date': ovulation_predict_date_str,
            'ovul_period': ovul_period,
            'luteum_pred_date': lm_pred_date_str,
            'luteum_period': lm_period

        }
        error_pred_tag = True
    else:
        result = None
        error_pred_tag = False

    return error_pred_tag, errors, result

graph = CommunityGraph()
for config in template_configs:
    node = TemplateNode(**config)
    graph.add_node(node)


def check_list(inputs, list_key, output, invalid_fields, validate_func):
    """
    检查输入的数据是否合规
    :param inputs: 输入数
    :param list_key:输入数据名
    :param invalid_fields: 错误字段
    :param validate_func: 输入的格式，1是全为正数对应周期长度、经期长度 ； 2为数字，可为正数或-1 对应血量、痛经；3为列表下字典 对应症状

    """
    values = inputs.get(list_key,[])
    # print('输入的数',values,list_key)
    if not isinstance(values, list):
        invalid_fields.append(False)
        return output, invalid_fields

    # 数据验证
    try:
        if validate_func == 1:
            # 正整数验证
            valid = all(isinstance(v, (int, float)) and v >= 1 for v in values)
        elif validate_func == 2:
            # 整数或-1验证
            valid = all(isinstance(v, (int, float)) and (v == -1 or v >= 0) for v in values)
        elif validate_func == 3:
            # 字典列表验证
            valid = all(
                isinstance(v, dict) and
                all(isinstance(val, (int, float)) and (val == -1 or val > 0) for val in v.values())
                for v in values
            )
        else:
            valid = False

        if valid:
            output[list_key] = values
            # 检查空列表情况
            if list_key in ['mens_blood_len_6', 'user_pain_records', 'user_symptom_records'] and len(values) == 0:
                invalid_fields.append(list_key)
        else:
            invalid_fields.append(False)

    except Exception as e:
        invalid_fields.append(False)

    return output, invalid_fields

def get_invalid_output(invalid_fields ,outputs):
    """
    用于 周期和痛经模块进入图前的数据校验
    """
    invalid_to_output_key ={'mens_cycle_len_6':['cycle_anlzr', 'cycle_diff_anlzr'],
                            'mens_period_len_6':['period_anlzr'],
                            'mens_blood_len_6':['blood_anlzr'],
                            'user_pain_records': ['pain_anlzr'],
                            'user_symptom_records': ['symptoms_anlzr'],

        }
    for field in invalid_fields:

        keys_to_clear = invalid_to_output_key.get(field)
        if keys_to_clear is None:  # 如果没有对应的 key，则跳过
            continue

        for key in keys_to_clear:
            outputs[key] = None

    return outputs


def gen_menstrual_cycles_monthly_report(cycles_input):
    """生成月经周期月报告"""

    error_info = {'error_type': 0, 'error_content': None, 'error_date': None}
    cycle_output = {
        'user_id': cycles_input['user_id'],
        'version': app_version,
        'error': error_info
    }

    # 数据检验和准备
    cycle_graph_input = {}
    invalid_fields = []
    check_items = [
        ('mens_period_len_6', 1),
        ('mens_cycle_len_6', 1),
        ('mens_blood_len_6', 2),
    ]

    for key, validate_func in check_items:
        try:
            cycle_graph_input, invalid_fields = check_list(
                cycles_input, key, cycle_graph_input, invalid_fields, validate_func
            )
        except Exception as e:
            if test_key:
                print(f"Error validating {key}: {e}")
            invalid_fields.append(False)

    if False in invalid_fields:
        error_info = {'error_type': 1, 'error_content': f'Invalid fields: {invalid_fields}', 'error_date': today_strf}
        return {
            'user_id': cycles_input['user_id'],
            'version': app_version,
            'cycle_anlzr': None,
            'cycle_diff_anlzr': None,
            'period_anlzr': None,
            'blood_anlzr': None,
            'error': error_info
        }

    cycle_output = get_invalid_output(invalid_fields, cycle_output)

    # 周期差异分析需要至少两个周期
    if cycle_graph_input.get('mens_cycle_len_6') and len(cycle_graph_input['mens_cycle_len_6']) < 2:
        cycle_output['cycle_diff_anlzr'] = None

    cycle_graph_input['user_age'] = cycles_input.get('user_age', 28)

    try:
        # 检索相关模板
        matched_templates = graph.match_templates(cycle_graph_input)
        cycle_templates = [t for t in matched_templates if t.community == '周期统计']

        if len(cycle_templates) == 0:
            error_info = {'error_type': 2, 'error_content': 'No template found', 'error_date': today_strf}
            cycle_output['error'] = error_info
            return cycle_output

        # 匹配文案
        texts_info = []
        for t in cycle_templates:
            try:
                group = t.group
                output_key = group_to_output_key.get(group)

                if output_key:
                    template_key = document_key.get(t.id)
                    if not template_key:
                        if test_key:
                            print(f"Warning: No template key found for ID {t.id}")
                        continue

                    try:
                        text_info, key_values, _ = t.render(cycle_graph_input, invalid_fields)
                    except Exception as e:
                        if test_key:
                            print(f"Template render error for {t.id}: {e}")
                        continue

                    if len(key_values) > 0:
                        template_info = {'key': template_key, 'values': key_values}
                        cycle_output[output_key] = template_info
                    else:
                        cycle_output[output_key] = None

                    if text_info:
                        texts_info.append(text_info)
                        
            except Exception as e:
                if test_key:
                    print(f"Error processing template: {e}")
                continue

        if test_key:
            return cycle_output, texts_info, error_info
        else:
            return cycle_output

    except Exception as e:
        error_info = {'error_type': 3, 'error_content': f'Processing error: {str(e)}', 'error_date': today_strf}
        cycle_output['error'] = error_info
        return cycle_output


def graph_to_analysis(user_data, report_key, anlzrs , anlzr_i, invalid_keys):
    """
    利用graph 给出周期分析
    :param user_data: 该模块对应的输入
    :param report_key: 周期报板块名
    :param anlzrs: dict或list ， 周期/症状对应dict ； 体温和hrv对应list
    :param anlzr_i: 每次运行的
    :param invalid_keys: 错误的字段

    """
    matched_templates = graph.match_templates(user_data)
    cycle_templates = [t for t in matched_templates if t.community == report_key]
    if len(cycle_templates) == 0:

        anlzrs.append(anlzr_i)
        return anlzrs

    for t in cycle_templates:
        group_id = t.group
        output_key = group_to_output_key.get(group_id, None)
        if output_key is None:
            anlzrs.append(anlzr_i)
        else:
            template_key = document_key.get(t.id)
            text_info, key_values, params_value = t.render(user_data, invalid_keys)

            if report_key == '周期统计' or report_key == '痛经与症状':
                template_info = {'key': template_key, 'values': key_values}
                anlzrs[output_key] = template_info
            elif report_key == '皮肤温度':

                anlzr_i.update({
                    'low_temp': params_value.get('low_temp', 35.5),
                    'high_temp': params_value.get('high_temp', 36.5),
                    'delta_temp': params_value.get('delta_temp', 0.3),
                    'temp_data_filter': params_value.get('real_filter_temp', []),
                    'anlzr': {'key': template_key, 'values': key_values},
                })
                anlzrs.append(anlzr_i)
            elif report_key == 'HRV与心情':
                anlzr_i.update({'anlzr': {'key': template_key, 'values': key_values}})
                anlzrs.append(anlzr_i)

    return anlzrs,anlzr_i

def gen_temperature_monthly_reports(temp_input):
    """生成体温月报告，处理所有异常场景"""

    temp_data_raw = temp_input.get('temp_data', [])
    if not temp_data_raw or not isinstance(temp_data_raw, list) or len(temp_data_raw) <= 0:
        return {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 1, 'error_content': 'temp data is empty or invalid', 'error_date': today_strf}
        }

    mens_dates = temp_input.get('menstrual_dates', [])
    if not mens_dates or not isinstance(mens_dates, list):
        return {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 2, 'error_content': 'menstrual_dates is empty or invalid', 'error_date': today_strf}
        }
        
    # 安全检查周期数据
    try:
        if not check_cycles(mens_dates):
            return {
                'user_id': temp_input['user_id'],
                'temp_anlzr': None,
                'version': app_version,
                'error': {'error_type': 3, 'error_content': 'menstrual_dates validation failed', 'error_date': today_strf}
            }
    except Exception as e:
        return {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 3, 'error_content': f'Error validating cycles: {str(e)}', 'error_date': today_strf}
        }
        
    # 安全排序周期数据
    try:
        sorted_dates = sorted(
            mens_dates,
            key=lambda x: datetime.strptime(x['mens_date'], '%Y-%m-%d'),
            reverse=True
        )
    except Exception as e:
        return {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 4, 'error_content': f'Error sorting dates: {str(e)}', 'error_date': today_strf}
        }

    try:
        expected_columns = ['date_sleep', 'temp_benchmark', 'temp_offset', 'flag']
        temp_data_raw = pd.DataFrame(temp_data_raw, columns=expected_columns)
        try:
            temp_data_raw.iloc[:, 1:] = temp_data_raw.iloc[:, 1:].astype(float)
        except (ValueError, TypeError) as e:
            return {
                'user_id': temp_input['user_id'],
                'temp_anlzr': None,
                'version': app_version,
                'error': {'error_type': 5, 'error_content': f'Error converting numeric data: {str(e)}', 'error_date': today_strf}
            }
            
        temp_data_raw['temp'] = temp_data_raw['temp_benchmark'] + temp_data_raw['temp_offset']
        
        # 日期转换
        try:
            temp_data_raw['date_sleep'] = pd.to_datetime(temp_data_raw['date_sleep'])
        except Exception as e:
            return {
                'user_id': temp_input['user_id'],
                'temp_anlzr': None,
                'version': app_version,
                'error': {'error_type': 5, 'error_content': f'Error converting dates: {str(e)}', 'error_date': today_strf}
            }
            
        temp_data_raw['error'] = 0

        # 数据预处理
        try:
            data_list, del_user = data_predo(temp_data_raw, 'temp')
        except Exception as e:
            return {
                'user_id': temp_input['user_id'],
                'temp_anlzr': None,
                'version': app_version,
                'error': {'error_type': 6, 'error_content': f'Error in data_predo: {str(e)}', 'error_date': today_strf}
            }
            
        if del_user == 1:
            data_frame = temp_data_raw.assign(filter=36.2)
        else:
            try:
                data_frame = pd.DataFrame(data_list)
                if data_frame.empty:
                    return {
                        'user_id': temp_input['user_id'],
                        'temp_anlzr': None,
                        'version': app_version,
                        'error': {'error_type': 6, 'error_content': 'Empty data after preprocessing', 'error_date': today_strf}
                    }
                    
                data_frame['date_sleep'] = pd.to_datetime(data_frame['date_sleep'])
                data_frame['filter'] = np.round(data_filter(data_frame['temp'], 3, 5, 2), 2)
            except Exception as e:
                return {
                    'user_id': temp_input['user_id'],
                    'temp_anlzr': None,
                    'version': app_version,
                    'error': {'error_type': 6, 'error_content': f'Error processing filtered data: {str(e)}', 'error_date': today_strf}
                }

    except Exception as e:
        return {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 6, 'error_content': f'Unexpected data processing error: {str(e)}', 'error_date': today_strf}
        }

    try :
        temp_anlzr = []
        mens_info = pd.DataFrame()
        for i in range(len(sorted_dates) - 1):

            end_date = pd.to_datetime(sorted_dates[i]['mens_date'])
            start_date = pd.to_datetime(sorted_dates[i + 1]['mens_date'])
            start_date_str = start_date.strftime('%Y-%m-%d')
            temp_data = data_frame[(data_frame['date_sleep'] >= start_date) & (data_frame['date_sleep'] < end_date)].copy()
            temp_data_wear = temp_data_raw[(temp_data_raw['date_sleep'] >= start_date) & (temp_data_raw['date_sleep'] < end_date)].copy()
            cycle_len = (end_date - start_date).days
            plt.plot(data_frame['date_sleep'],data_frame['temp'])
            plt.plot(data_frame['date_sleep'],data_frame['filter'])
            plt.show()
            ovulation_index = np.ceil(-5.2835+0.7344*cycle_len) if cycle_len > 20 else np.ceil(-2.2835+0.7344*cycle_len)
            # wear_temp_days = len(temp_data[temp_data['error'] == 0])
            wear_temp_days = len(temp_data_wear)
            wear_date_ratio = round(wear_temp_days / cycle_len, 2)

            temp_anlzr_i = {
                'start_date': start_date_str ,
                'period_len': next((d['period_len'] for d in sorted_dates if d['mens_date'] == start_date), 5),
                'low_temp': None,
                'high_temp': None,
                'delta_temp': None,
                'temp_data_filter': None,
                'anlzr': None,
                }
            if wear_temp_days <= 0:

                temp_anlzr.append(temp_anlzr_i)
            else:
                temp_input_i = {'temp_data': temp_data[['date_sleep', 'filter']],
                                'start_date': start_date,
                                'end_date': end_date,
                                'ovul_date': start_date + timedelta(int(ovulation_index)),
                                'ovulation_index':int(ovulation_index),
                                'cycle_days':int(cycle_len),
                                'wear_temp_days': wear_temp_days,
                                'wear_date_ratio': wear_date_ratio
                                }

                matched_templates = graph.match_templates(temp_input_i)
                cycle_templates = [t for t in matched_templates if t.community == '皮肤温度']

                if len(cycle_templates) == 0:
                    temp_anlzr.append(temp_anlzr_i)
                    error_info = {'error_type': 7, 'error_content': 'No template found', 'error_date': today_strf}
                    continue

                for t in cycle_templates:
                    group_id = t.group
                    output_key = group_to_output_key.get(group_id,None)

                    if output_key is None:
                        temp_anlzr.append(temp_anlzr_i)
                    else:
                        template_key = document_key.get(t.id)
                        text_info, key_values, params_value = t.render(temp_input_i,[])

                        temp_anlzr_i.update({
                            'low_temp': params_value.get('low_temp'),
                            'high_temp': params_value.get('high_temp'),
                            'delta_temp': params_value.get('delta_temp'),
                            'temp_data_filter': params_value.get('real_filter_temp'),
                            'anlzr': {'key': template_key, 'values': key_values},
                        })
                        temp_anlzr.append(temp_anlzr_i)
                # -------------------------------------测试需要开始------------------------------------------------------------
                        # 填数据
                        mens_info.loc[i, 'user_id'] = temp_input['user_id']
                        mens_info.loc[i, 'start_date'] = start_date_str
                        mens_info.loc[i, 'mens_cycle'] = cycle_len
                        mens_info.loc[i, 'wear_days'] = wear_temp_days
                        mens_info.loc[i, 'anlzr_content'] = text_info

                if test_key:
                    # 画图
                    if temp_anlzr_i['temp_data_filter'] is not None:
                        # filter_temp_data = pd.DataFrame(temp_anlzr_i['temp_data_filter'], columns=['date_sleep', 'temp'])
                        temp_data['date_sleep'] = pd.to_datetime(temp_data['date_sleep'])
                        low_temp = temp_anlzr_i['low_temp']
                        high_temp = temp_anlzr_i['high_temp']
                        plt.plot(temp_data['date_sleep'], temp_data['temp'].astype(float), label='raw temp')
                        plt.plot(temp_data['date_sleep'], temp_data['filter'].astype(float), label='filter temp')
                        ovulation_date= start_date + timedelta(ovulation_index)
                        plt.axvline(x=start_date, color='grey', linestyle='--')
                        plt.axvline(x=end_date, color='grey', linestyle='--')
                        plt.axvline(x= ovulation_date, color='grey', linestyle='--')
                        plt.axhline(y=high_temp, color='r', linestyle='--', label='high temp')
                        plt.axhline(y=low_temp, color='g', linestyle='--', label='low temp')
                        arrow_x = len(temp_data) // 2
                        delta = temp_anlzr_i['delta_temp']
                        plt.annotate(
                            f'ΔT = {delta:.2f}°C',
                            xy=(arrow_x, temp_anlzr_i['high_temp']),  # 箭头终点（高温）
                            xytext=(arrow_x, low_temp),  # 箭头起点（低温）
                            arrowprops=dict(
                                arrowstyle='<->',  # 双向箭头！
                                color='purple',
                                lw=2
                            ),
                            ha='center',  # 水平居中
                            va='center',  # 垂直居中
                            fontsize=10,
                            color='purple'
                        )
                        user_id = temp_input['user_id']
                        plt.xticks(temp_data['date_sleep'][::3], rotation=90)
                        plt.legend()
                        plt.title(f'id:{user_id}, delta : {delta:.2f},low:{low_temp}, high:{high_temp}',)

                        # plt.savefig(
                        #     rf'D:\研发部-算法\生理期\生理期-hq\menstrual_interactive\数据\测试标签系统\周期报画图\temp\{user_id}_temp_{i}.png')
                        plt.show()
                        plt.close()


        # 记录内容
        mens_info.loc[len(sorted_dates), 'inputs'] = str(temp_input)
        mens_info.loc[len(sorted_dates), 'output'] = str(temp_anlzr)
        mens_info.loc[len(sorted_dates), 'len_mens'] = len(temp_input['menstrual_dates'])
        mens_info.loc[len(sorted_dates), 'mens_dates'] = str(temp_input['menstrual_dates'])

        # --------------------------------------------测试需要结束-------------------------------------------------------------

        cycle_output = {
            'user_id': temp_input['user_id'],
            'temp_anlzr': temp_anlzr,
            'version': app_version,
            'error': {'error_type': 0, 'error_content': None, 'error_date': None}

        }
    except:

        cycle_output = {
            'user_id': temp_input['user_id'],
            'temp_anlzr': None,
            'version': app_version,
            'error': {'error_type': 7, 'error_content': 'processing error', 'error_date': None}

        }
    if test_key:
        return cycle_output, mens_info
    else:
        return cycle_output

def cal_symptoms_info(symptom_records):
    period_keys = ['mens', 'folli', 'ovula', 'lm']
    result = {
        vals.get('symptom', 0):
            sum(
                vals.get(key, 0)  # 键不存在时视为0，不影响总和
                for key in period_keys
                if vals.get(key, 0) > 0  # 只累加>0的值
            )
        for vals in symptom_records
    }

    # 找出最大值对应的 key
    sorted_items = sorted(result.items(), key=lambda x: x[1], reverse=True)
    # 最大和第二大
    max_symptom_key, max_value = sorted_items[0]
    second_symptom_key, second_value = sorted_items[1] if len(sorted_items) > 1 else (None, None)

    def get_max_period(symptom_key):
        target_record = next((r for r in symptom_records if r['symptom'] == symptom_key), None)
        if not target_record:
            return None, None
        period_values = {k: target_record[k] for k in period_keys}
        max_period, max_value = max(period_values.items(), key=lambda x: x[1])
        return max_period, max_value

    max_period, max_period_value = get_max_period(max_symptom_key)
    second_period, second_period_value = get_max_period(second_symptom_key) if second_symptom_key else (
        None, None)

    # 比较两个症状是否出现在同一时期
    if max_period and second_period:
        same_period = (max_period == second_period)
    else:
        same_period = False

    symptom_info = { 'max_symptom_key':max_symptom_key,
                     'max_value':max_period,
                     'second_symptom_key':second_symptom_key ,
                     'second_value': second_period,
                     'same_period':same_period

    }
    # print('症状计算',symptom_info)
    return symptom_info
def gen_pain_symptoms_monthly_report(pain_symptom_input):
    """
    生成痛经和症状月报告，处理所有异常场景
    返回完整的报告结果或错误信息
    """

    error_info = {'error_type': 0, 'error_content': None, 'error_date': None}
    pain_symptom_output = {
        'user_id': pain_symptom_input['user_id'],
        'version': app_version,
        'error': error_info
    }

    try:
        pain_graph_input = {}
        invalid_fields = []
        check_items = [
            ('user_pain_records', 2),
            ('user_pain_total_cycles', 1),
            ('user_symptom_records', 3),
        ]

        # 数据检验
        for key, validate_func in check_items:
            try:
                pain_graph_input, invalid_fields = check_list(
                    pain_symptom_input, key, pain_graph_input, invalid_fields, validate_func
                )
            except Exception as e:
                print(f"Error validating {key}: {e}")
                invalid_fields.append(False)

        if False in invalid_fields:
            error_info = {'error_type': 1, 'error_content': f'Invalid fields: {invalid_fields}', 'error_date': today_strf}
            return {
                'user_id': pain_symptom_input['user_id'],
                'version': app_version,
                'pain_anlzr': None,
                'symptoms_anlzr': None,
                'error': error_info
            }

        # 处理无效字段
        pain_symptom_output = get_invalid_output(invalid_fields, pain_symptom_output)

        if pain_graph_input.get('user_symptom_records'):
            try:
                symptom_info = cal_symptoms_info(pain_graph_input['user_symptom_records'])
                # 检查症状信息是否有效
                if symptom_info and symptom_info.get('max_symptom_key') is not None:
                    pain_graph_input['symptom_info'] = symptom_info
                else:
                    error_info = {'error_type': 2, 'error_content': f'cont find max_symptom_key',
                                  'error_date': today_strf}
                    return {
                            'user_id': pain_symptom_input['user_id'],
                            'version': app_version,
                            'pain_anlzr': None,
                            'symptoms_anlzr': None,
                            'error': error_info
                        }

            except Exception as e:
                error_info = {'error_type': 2, 'error_content': f'cont find max_symptom_key',
                              'error_date': today_strf}

                return {
                    'user_id': pain_symptom_input['user_id'],
                    'version': app_version,
                    'pain_anlzr': None,
                    'symptoms_anlzr': None,
                    'error': error_info
                }

        # 匹配模板
        try:
            matched_templates = graph.match_templates(pain_graph_input)
            cycle_templates = [t for t in matched_templates if t.community == '痛经与症状']
        except Exception as e:
            print(f"Error matching templates: {e}")
            cycle_templates = []

        if len(cycle_templates) == 0:
            error_info = {'error_type': 3, 'error_content': 'No template found', 'error_date': today_strf}
            pain_symptom_output['error'] = error_info
            return pain_symptom_output

        # 渲染模板
        texts_info = []
        for t in cycle_templates:
            try:
                group = t.group
                output_key = group_to_output_key.get(group)

                if output_key:
                    # 检查是否已经存在该输出
                    if output_key in pain_symptom_output:
                        continue

                    template_key = document_key.get(t.id)
                    if not template_key:
                        if test_key:
                            print(f"Warning: No template key found for ID {t.id}")
                        continue

                    try:
                        text_info, key_values, _ = t.render(pain_graph_input, invalid_fields)

                        if key_values and len(key_values) > 0:
                            template_info = {'key': template_key, 'values': key_values}
                            pain_symptom_output[output_key] = template_info
                        else:
                            pain_symptom_output[output_key] = None

                        if text_info:
                            texts_info.append(text_info)

                    except Exception as render_error:
                        if test_key:
                            print(f"Error rendering template {t.id}: {render_error}")
                        pain_symptom_output[output_key] = None

            except Exception as template_error:
                if test_key:
                    print(f"Error processing template: {template_error}")
                continue

        if test_key:
            return pain_symptom_output, texts_info, error_info
        else:
            return pain_symptom_output

    except Exception as e:
        error_info = {'error_type': 4, 'error_content': f'Processing error: {str(e)}', 'error_date': today_strf}
        return {
                'user_id': pain_symptom_input['user_id'],
                'version': app_version,
                'pain_anlzr': None,
                'symptoms_anlzr': None,
                'error': error_info
            }


def check_mood_records(mood_records) -> bool:
    if not isinstance(mood_records, list):
        return False

    dates = set()
    for item in mood_records:
        if not isinstance(item, dict):
            return False

        if "date_sleep" not in item or "mood" not in item:
            return False

        date = item["date_sleep"]
        mood = item["mood"]

        if date in dates:
            return False
        dates.add(date)

        if not isinstance(mood, (int, float)) or mood <= 0:
            return False

    return True

def gen_hrv_mood_monthly_reports(hrv_input):
    # 经期
    mens_dates = hrv_input['menstrual_dates']
    mens_key = check_cycles(mens_dates)
    if not mens_key:
        error_info = {'error_type': 2, 'error_content': 'menstrual_dates is wrong', 'error_date': today_strf}
        return {
            'user_id': hrv_input['user_id'],
            'hrv_anlzr': None,
            'version': app_version,
            'error': error_info
        }

    mens_cycles, error = create_every_cycles(mens_dates)
    if error['error_type'] != 0:
        error_info = {'error_type': 3, 'error_content': 'create_every_cycles error', 'error_date': today_strf}
        return {
            'user_id': hrv_input['user_id'],
            'hrv_anlzr': None,
            'version': app_version,
            'error': error_info

        }
    sorted_dates = sorted(
        mens_cycles,
        key=lambda x: datetime.strptime(x['mens_pred_date'], '%Y-%m-%d'),
        reverse=False
    )

    # hrv数据
    hrv_data = hrv_input['hrv_data']
    if len(hrv_data) <= 0 :
        error_info = {'error_type': 1, 'error_content': 'hrv is empty', 'error_date': today_strf}
        return {
        'user_id': hrv_input['user_id'],
        'hrv_anlzr': None,
        'version': app_version,
        'error': error_info

        }

    hrv_data_df = pd.DataFrame(hrv_data, columns=['date_sleep', 'hr_avg', 'hrv_avg', 'flag'])
    hrv_data_df.iloc[:, 1:] = hrv_data_df.iloc[:, 1:].astype(float)
    hrv_data_df['date_sleep'] = pd.to_datetime(hrv_data_df['date_sleep'])

    # 心情
    mood_records = hrv_input['mood_records']
    mood_key = check_mood_records(mood_records)
    if not mood_key:
        error_info = {'error_type': 4, 'error_content': 'mood_records error', 'error_date': today_strf}
        return {
            'user_id': hrv_input['user_id'],
            'hrv_anlzr': None,
            'version': app_version,
            'error': error_info

        }
    mood_records = pd.DataFrame(mood_records)
    if not mood_records.empty and 'date_sleep' in mood_records.columns:
        mood_records['date_sleep'] = pd.to_datetime(mood_records['date_sleep'])
    try:
        # 遍历每个周期
        hrv_anlzr = []
        hrv_info = pd.DataFrame()
        for i in range(len(sorted_dates) ):

            end_date = pd.to_datetime(sorted_dates[i]['mens_next_date'])
            start_date = pd.to_datetime(sorted_dates[i]['mens_pred_date'])
            start_date_str = start_date.strftime('%Y-%m-%d')

            hr_data_i = hrv_data_df[(hrv_data_df['date_sleep'] >= start_date) & (hrv_data_df['date_sleep'] < end_date)].copy()
            if mood_records.empty:
                mood_records_i = pd.DataFrame()
            else:
                mood_records_i = mood_records[(mood_records['date_sleep'] >= start_date) & (mood_records['date_sleep'] < end_date)].copy()

            # cycle_len = (end_date - start_date).days
            cycle_len = sorted_dates[i]['mens_cycle']
            period_len= sorted_dates[i]['mens_period']
            wear_temp_days = len(hr_data_i)
            wear_date_ratio = round(wear_temp_days / cycle_len, 2) if cycle_len > 0 else 0
            invalid_fields = ['hrv_data'] if wear_temp_days == 0 else []
            # print( 'mens_cycles', mens_cycles)
            hrv_anlzr_i = {
                'start_date': start_date_str,
                'period_len': period_len,
                'anlzr': None,
            }
            if wear_temp_days <= 0 : # 该周期没hrv数据
                hrv_anlzr.append(hrv_anlzr_i)
                error_info = {'error_type': 5, 'error_content': f'hrv_data_{i+1} is empty', 'error_date': today_strf}
            else:
                hrv_mood_input = {'hrv_data': hr_data_i,
                                  'mens_cycle': mens_cycles[i],
                                  'mood_records': mood_records_i,
                                  'wear_data_ratio': wear_date_ratio
                                  }

                hrv_mood_info, hrv_input_valid = cal_hrv_params(hrv_mood_input) # 判断类型情况，因为需要有数据才能计算心情与hrv类型。
                if not hrv_input_valid:

                    hrv_anlzr_i = {'start_date': start_date_str,
                                    'period_len': period_len,
                                    'anlzr': {'key': 'keyPeriodForecastsHRVAndMoodFrequentError','values': []} # 产品给文案
                                   }
                    hrv_anlzr.append(hrv_anlzr_i)
                    error_info = {'error_type': 6, 'error_content': f'cycle maybe is wrong', 'error_date': today_strf}
                    continue

                matched_templates = graph.match_templates(hrv_mood_info)
                cycle_templates = [t for t in matched_templates if t.community == 'HRV与心情']
                if len(cycle_templates) == 0:

                    hrv_anlzr.append(hrv_anlzr_i)
                    error_info = {'error_type': 7, 'error_content': 'No template found', 'error_date': today_strf}
                    continue

                for t in cycle_templates:
                    group_id = t.group
                    output_key = group_to_output_key.get(group_id, None)
                    if output_key is None:

                        hrv_anlzr.append(hrv_anlzr_i)
                        error_info = {'error_type': 8, 'error_content': 'Not found group_to_output_key', 'error_date': today_strf}
                    else:
                        template_key = document_key.get(t.id)
                        text_info, key_values, params_value = t.render(hrv_mood_info, invalid_fields)

                        hrv_anlzr_i.update({'anlzr': {'key': template_key,'values': key_values}})

                        hrv_anlzr.append(hrv_anlzr_i)
                        error_info = {'error_type': 0, 'error_content': None, 'error_date': None}
                # -------------------------------------测试需要开始------------------------------------------------------------
                        hrv_info.loc[i, 'user_id'] = hrv_input['user_id']
                        hrv_info.loc[i, 'start_date'] = start_date_str
                        hrv_info.loc[i, 'mens_cycle'] = cycle_len
                        hrv_info.loc[i, 'wear_days'] = wear_temp_days
                        hrv_info.loc[i, 'anlzr_content'] = text_info

                # 画图
                if test_key:
                    if wear_date_ratio > 0.5 and len(mood_records) > 0:
                        # filter_temp_data = pd.DataFrame(temp_anlzr_i['temp_data_filter'], columns=['date_sleep', 'temp'])
                        hr_data_i['date_sleep'] = pd.to_datetime(hr_data_i['date_sleep'])
                        mood_records = pd.DataFrame(mood_records)
                        mood_records['date_sleep'] = pd.to_datetime(mood_records['date_sleep'])
                        mood_map = {
                            1: ("red", "happy"),
                            2: ("blue", "calm"),
                            3: ("green", "tired"),
                            4: ("orange", "anxious"),
                            5: ("purple", "depressed")
                        }

                        used_labels = set()  # 避免图例重复
                        for _, row in mood_records_i.iterrows():
                            # 找到对应日期的 hrv_avg
                            hrv_val = hr_data_i.loc[hr_data_i["date_sleep"] == row["date_sleep"], "hrv_avg"].values
                            if len(hrv_val) == 0:
                                continue  # 没有匹配就跳过
                            hrv_val = float(hrv_val[0])

                            color, label = mood_map[row["mood"]]
                            # 避免重复 legend
                            plt.scatter(row["date_sleep"], hrv_val, color=color, label=label if label not in used_labels else "")
                            used_labels.add(label)
                        plt.plot(hr_data_i['date_sleep'], hr_data_i['hrv_avg'].astype(float), label='hrv')

                        user_id = hrv_input['user_id']
                        plt.xticks(hr_data_i['date_sleep'][::3], rotation=90)
                        plt.legend()
                        plt.title(f'id:{user_id}' )
                        plt.savefig(rf'D:\研发部-算法\生理期\生理期-hq\menstrual_interactive\数据\测试标签系统\周期报画图\hrv\{user_id}_hrv_{i}.png')
                        plt.show()
                        plt.close()

        # 记录内容
        hrv_info.loc[len(sorted_dates), 'len_mens'] = len(hrv_input['menstrual_dates'])
        hrv_info.loc[len(sorted_dates), 'mens_dates'] = str(hrv_input['menstrual_dates'])
        hrv_info.loc[len(sorted_dates), 'inputs'] = str(hrv_input)
        hrv_info.loc[len(sorted_dates), 'output'] = str(hrv_anlzr)
        # --------------------------------------------测试需要结束-------------------------------------------------------------

        hrv_output = {
            'user_id': hrv_input['user_id'],
            'hrv_anlzr': hrv_anlzr,
            'version': app_version,
            'error': {'error_type': 0, 'error_content': None, 'error_date': None}

        }
    except:
        hrv_output = {
            'user_id': hrv_input['user_id'],
            'hrv_anlzr': None,
            'version': app_version,
            'error': {'error_type': 9, 'error_content': 'hrv anlzr error', 'error_date': None}

        }

    if test_key:
        return hrv_output, hrv_info,error_info
    else:
        return hrv_output


def cal_hrv_params(user_data):
    """
    计算HRV和心情相关参数，处理所有异常场景
    只返回四种支持的hrv_mood_key情况
    返回: (hrv_params, is_valid)
    """
    # 初始化默认值
    hrv_params = {
        'hrv_pattern': None,
        'hrv_trend': None,
        'pos_des': None,
        'neg_des': None,
        'hrv_mood_key': 'hrv data ≤50%',  # 默认为数据不足
        'hrv_data': []
    }
    
    # 输入数据验证
    if not isinstance(user_data, dict):
        return hrv_params, False
        
    hrv_data = user_data.get("hrv_data")
    menstrual_cycle = user_data.get("mens_cycle")
    mood_records = user_data.get("mood_records", [])
    wear_date_ratio = user_data.get("wear_data_ratio", 0)
    
    # 检查HRV数据覆盖率
    if wear_date_ratio < 0.5:
        hrv_params['hrv_mood_key'] = 'hrv data ≤50%'
        return hrv_params, True
    
    # 计算HRV标签
    try:
        hrv_tag, hrv_key = cal_hrv_tag(hrv_data, menstrual_cycle)
        
        if not hrv_key:
            # HRV计算失败，归类为数据不足
            hrv_params['hrv_mood_key'] = 'hrv data ≤50%'
            return hrv_params, False
        
        # 获取HRV相关参数
        hrv_params['hrv_pattern'] = hrv_tag.get('hrv_pattern')
        hrv_params['hrv_trend'] = hrv_tag.get('hrv_trend')
        
        # 处理心情数据
        if isinstance(mood_records, pd.DataFrame):
            mood_df = mood_records
        elif isinstance(mood_records, list) and len(mood_records) > 0:
            try:
                mood_df = pd.DataFrame(mood_records)
            except Exception:
                mood_df = pd.DataFrame()
        else:
            mood_df = pd.DataFrame()
            
        # 检查心情数据是否为空
        if mood_df.empty or 'mood' not in mood_df.columns:
            hrv_params['hrv_mood_key'] = 'no mood record'
            return hrv_params, True
            
        # 检查HRV数据是否可用于心情分析
        if hrv_data is None or hrv_data.empty:
            hrv_params['hrv_mood_key'] = 'no mood record'  # 没有HRV数据，归类为没有心情记录
            return hrv_params, True
            
        try:
            # 合并HRV和心情数据
            merged = pd.merge(hrv_data, mood_df, how='inner', on='date_sleep')
            
            if merged.empty:
                hrv_params['hrv_mood_key'] = 'no mood record'  # 数据不匹配，归类为没有心情记录
                return hrv_params, True
                
            # 分类正向和负向情绪
            pos_hrv_mood = merged[merged['mood'].isin([1, 2])]['hrv_avg']
            neg_hrv_mood = merged[merged['mood'].isin([3, 4, 5, 6, 7])]['hrv_avg']
            
            pos_count = len(pos_hrv_mood)
            neg_count = len(neg_hrv_mood)
            
            if test_key:
                print(f'心情HRV分析: 正向情绪{pos_count}条, 负向情绪{neg_count}条')
                print(f'正向HRV数据: {pos_hrv_mood.tolist()}')
                print(f'负向HRV数据: {neg_hrv_mood.tolist()}')
            
            # 判断情绪数据的完整性 - 所有异常情况都归类为'hrv and mood unrelated'
            if pos_count == 0 or neg_count == 0:
                hrv_params['hrv_mood_key'] = 'hrv and mood unrelated'
                return hrv_params, True
            
            # 计算均值和差异
            pos_mean = pos_hrv_mood.mean()
            neg_mean = neg_hrv_mood.mean()
            
            # 检查是否有NaN值
            if np.isnan(pos_mean) or np.isnan(neg_mean):
                hrv_params['hrv_mood_key'] = 'hrv and mood unrelated'
                return hrv_params, True
                
            # 计算差异比例
            max_mean = max(pos_mean, neg_mean)
            if max_mean == 0:
                diff_ratio = 0
            else:
                diff_ratio = abs(pos_mean - neg_mean) / max_mean
            
            if test_key:
                print(f'HRV心情关联分析: pos_mean={pos_mean:.2f}, neg_mean={neg_mean:.2f}, diff_ratio={diff_ratio:.3f}')
            
            # 判断关联性，只返回两种结果
            if diff_ratio < 0.05 or np.isnan(diff_ratio):
                hrv_params['hrv_mood_key'] = 'hrv and mood unrelated'
            else:
                hrv_params['pos_des'] = "高" if pos_mean > neg_mean else "低"
                hrv_params['neg_des'] = "高" if neg_mean > pos_mean else "低"
                hrv_params['hrv_mood_key'] = 'hrv and mood related'
                
        except Exception as e:
            if test_key:
                print(f"Error in mood analysis: {e}")
            hrv_params['hrv_mood_key'] = 'hrv and mood unrelated'
            
    except Exception as e:
        if test_key:
            print(f"Error in cal_hrv_params: {e}")
        hrv_params['hrv_mood_key'] = 'hrv data ≤50%'
        return hrv_params, False
    
    return hrv_params, True


def cal_hrv_tag(data, mens_cycle):
    """
    计算hrv标签，处理所有异常场景
    返回: (hrv_tag, is_valid)
    """
    # 初始化默认值
    hrv_tag = {
        'hrv_pattern': None,
        'hrv_trend': None, 
        'hrv_avg': 0,
        'hrv_compare': False,
        'data_status': 'insufficient_data'  # 数据状态标识
    }
    
    # 检查必要的日期字段
    required_keys = ["mens_pred_date", "ovul_pred_date", "mens_next_date"]
    if not isinstance(mens_cycle, dict) or any(mens_cycle.get(k) is None for k in required_keys):
        hrv_tag['data_status'] = 'missing_cycle_dates'
        return hrv_tag, False

    # 检查HRV数据是否存在且有效
    if data is None or data.empty or 'hrv_avg' not in data.columns:
        hrv_tag['data_status'] = 'no_hrv_data'
        return hrv_tag, False

    try:
        mens_date = change_time(mens_cycle, 'mens_pred_date')
        ovula_date = change_time(mens_cycle, 'ovul_pred_date')
        mens_date_next = change_time(mens_cycle, 'mens_next_date')
        
        # 检查日期顺序是否合理
        if not (mens_date < ovula_date < mens_date_next):
            hrv_tag['data_status'] = 'invalid_date_sequence'
            return hrv_tag, False

        # 分别获取卵泡期和黄体期的HRV数据
        folli_hrv_data = data[(data['date_sleep'] >= mens_date) & (data['date_sleep'] < ovula_date)]
        lm_hrv_data = data[(data['date_sleep'] > ovula_date) & (data['date_sleep'] < mens_date_next)]
        hrv_data = data[(data['date_sleep'] >= mens_date) & (data['date_sleep'] < mens_date_next)]
        
        # 检查数据有效性
        folli_count = len(folli_hrv_data)
        lm_count = len(lm_hrv_data)
        total_count = len(hrv_data)
        
        if test_key:
            print(f'HRV数据统计: 卵泡期{folli_count}天, 黄体期{lm_count}天, 总计{total_count}天')
            print(f'日期范围: {mens_date.date()} - {ovula_date.date()} - {mens_date_next.date()}')
        
        # 计算整个周期的HRV均值
        if total_count > 0:
            hrv_avg = np.mean(hrv_data['hrv_avg'])
            if np.isnan(hrv_avg):
                hrv_avg = 0
        else:
            hrv_avg = 0
            
        hrv_tag['hrv_avg'] = hrv_avg
        
        # 判断能否进行比较
        if folli_count == 0 and lm_count == 0:
            hrv_tag['data_status'] = 'no_phase_data'
            return hrv_tag, False
        elif folli_count == 0:
            hrv_tag['data_status'] = 'only_luteal_data'
            hrv_tag['hrv_pattern'] = None
            hrv_tag['hrv_trend'] = None
            hrv_tag['hrv_compare'] = False
            return hrv_tag, True
        elif lm_count == 0:
            hrv_tag['data_status'] = 'only_follicular_data'
            hrv_tag['hrv_pattern'] = None
            hrv_tag['hrv_trend'] = None
            hrv_tag['hrv_compare'] = False
            return hrv_tag, True
        else:
            # 两个阶段都有数据，可以进行比较
            folli_hrv_avg = np.mean(folli_hrv_data['hrv_avg'])
            lm_hrv_avg = np.mean(lm_hrv_data['hrv_avg'])
            
            # 检查是否有NaN值
            if np.isnan(folli_hrv_avg) or np.isnan(lm_hrv_avg):
                hrv_tag['data_status'] = 'invalid_hrv_values'
                return hrv_tag, False
                
            hrv_diff_value = folli_hrv_avg - lm_hrv_avg
            
            if test_key:
                print(f'HRV对比: 卵泡期均值={folli_hrv_avg:.2f}, 黄体期均值={lm_hrv_avg:.2f}, 差值={hrv_diff_value:.2f}')
            
         
        
            hrv_tag['hrv_pattern'] = "高于" if hrv_diff_value > 0 else "低于"
            hrv_tag['hrv_trend'] = "相符" if hrv_diff_value > 0 else "不符"
            hrv_tag['hrv_compare'] = True
            hrv_tag['data_status'] = 'comparable_different'
            
            return hrv_tag, True
            
    except Exception as e:
        print(f"Error in cal_hrv_tag: {e}")
        hrv_tag['data_status'] = 'calculation_error'
        return hrv_tag, False